'File....: SE02.Bas
'
'Author..: Simon Whittam
'
'Email...: s.whittam@xnet.co.nz
'
'Date....: 8th September 2013
'
'Language: Maximite BASIC, v4.4
'
'Purpose.: To provide an example of a Event Driven framework that:
'            uses circular First In, First Out (FIFO) event queues comprised of String Arrays.
'            debounces switch inputs.
'            uses Bit flags stored in a numeric variable.
'            is comprised of two co-operating Finite State Machines (FSM).
'            uses non blocking timers (10ms and 1sec) for delays.
'            uses events to move between states and communicate between FSM's.
'            displays State/Event changes
'            the use of pointer to a SUB (ON var GOSUB A0,A1,A2,...)
' 
'          The code example turns on & off the toggling of a LED only when a pushbutton
'          switch has been pushed 3 times and remains pushed, all within 2 seconds.
'
'Version.: v1.1
'
'License.: Attribution-NonCommercial-ShareAlike 3.0 Australia (CC BY-NC-SA 3.0 AU)
'
'Ref.....: http://www.state-machine.com/qm/
'          http://geoffg.net/MonoMaximite.html
'          http://mmbasic.com/
'          http://creativecommons.org/licenses/by-nc-sa/3.0/au/
'
'==============================================================================

Library Load "Bit.Lib"


'BEGIN EventQ1.Lib - Initialisation

   Library Load "EventQ1.Lib"

   Option BASE 0     'event queue data referenced as an offset from: 0 - (Length -1)

   'Configure event queue as empty
   EQ1Length    = 4  'Event queue length.
   EQ1WriteFlag = 1  'Gets toggled after writing to last location in event queue.
   EQ1ReadFlag  = 1  'Gets toggled after reading last location in event queue.
   EQ1NextWrite = 0  'Next location to write a byte.
   EQ1NextRead  = 0  'Next location to read a byte.

   Dim EQ1$(1) LENGTH EQ1Length  'Create a circular event queue using a string/byte array.

   EQ1$ = "FIFO"

'END EventQ1.Lib - Initialisation

'==============================================================================

'BEGIN EventQ2.Lib - Initialisation

   Library Load "EventQ2.Lib"

   'Option BASE 0     'event queue data referenced as an offset from: 0 - (Length -1)

   'Configure event queue as empty
   EQ2Length    = 4  'Event queue length.
   EQ2WriteFlag = 1  'Gets toggled after writing to last location in event queue.
   EQ2ReadFlag  = 1  'Gets toggled after reading last location in event queue.
   EQ2NextWrite = 0  'Next location to write a byte.
   EQ2NextRead  = 0  'Next location to read a byte.

   Dim EQ2$(1) LENGTH EQ2Length  'Create a circular event queue using a string/byte array.

   EQ2$ = "FIFO"

'END EventQ2.Lib - Initialisation

'==============================================================================
'Define I/O pins used
Pin(11) = 1      ' Pin 11 -> 1, LED off

SetPin 12, 2     ' Pin 12, Input, 5v Digital, with external pull-up resistor
SetPin 11, 9     ' Pin 11, Output, Open Collector, with LED & resistor to 5Vdc. 


'SysFlags bit assignments
'  SFTick10ms = 0
'  SFTick1s   = 1
'  SFSwitch   = 2
'  SFLED      = 3

SysFlags     = &B0000


' Queue1 event definitions used
'   Undefined1     = 0
'   Switch1.0      = 1
'   Switch1.1      = 2
'   TickTimeout1   = 3
'   SecTimeout1    = 4

' Queue2 event definitions used
'   Undefined2   = 0
'   TickTimeout2 = 1
'   Enable2      = 2
'   Disable2     = 3

'
SetTick   10, BitSet10ms, 1
SetTick 1000, BitSet1s  , 2

' Queue1 variables
'=================
SecTimeout1          = 0
TickTimeout1         = 0
Switch1DebounceCount = 0
CurState1            = 0  ' Current State
PrvState1            = 0  ' Previous State
CurEvent1            = 0  ' Undefined event
CurStateEvent1       = 0
PrvStateEvent1       = 0

' Queue2 variables
'=================
TickTimeout2         = 0
CurState2            = 0  ' Current State
PrvState2            = 0  ' Previous State
CurEvent2            = 0  ' Undefined event
CurStateEvent2       = 0
PrvStateEvent2       = 0

Do While (1)

   '===========================================================================

   'BEGIN Debug1
   '  PrvStateEvent1 = CurStateEvent1
   'END Debug1


   'Respond to events from Queue1 for Finite State Machine 1
   CurEvent1      = EQ1Read()
   CurStateEvent1 = ( Fix( (CurState1 * 5) + CurEvent1 ) + 1 )


   'BEGIN Debug1
   '  Print only first occurrence of State/Event change
   '  IF NOT( PrvStateEvent1 = CurStateEvent1 ) THEN
   '     PRINT "StateEvent1: " + STR$( CurStateEvent1 )
   '  ENDIF
   'END Debug1


   On CurStateEvent1 GoSub A0,A1,A2,A3,A4,B0,B1,B2,B3,B4

   '===========================================================================

   'BEGIN Debug2
   '  PrvStateEvent2 = CurStateEvent2
   'END Debug2


   'Respond to events from Queue2 for Finite State Machine 2
   CurEvent2      = EQ2Read()
   CurStateEvent2 = ( Fix( (CurState2 * 4) + CurEvent2 ) + 1 )


   'BEGIN Debug2
   '  Print only first occurrence of State/Event change
   '  IF NOT( PrvStateEvent2 = CurStateEvent2 ) THEN
   '     PRINT "StateEvent2: " + STR$( CurStateEvent2 )
   '  ENDIF
   'END Debug2


   On CurStateEvent2 GoSub E0,E1,E2,E3,F0,F1,F2,F3,G0,G1,G2,G3

   '===========================================================================


   'Has SFTick10ms flag been set
   IF BitTstSet( SysFlags, 0 )  THEN

      DecTimeOut1( TickTimeout1, 3)

      DecTimeout2( TickTimeout2, 1)

      'DecTimeoutN( TickTimeoutN, TickTimeoutEventN)

      DebounceSwitch1

      'DebounceSwitchN


      SysFlags = BitClr( SysFlags, 0 )  'Clear SFTick10ms flag.

   ENDIF


   'Has SFTick1s flag been set
   If BitTstSet( SysFlags, 1 )  Then

      DecTimeout1( SecTimeout1, 4 )

      'DecTimeoutN( SecTimeoutN, TimeoutNEvent )

      SysFlags = BitClr( SysFlags, 1 )  'Clear SFTick1s flag.
 
   ENDIF

Loop

Print "Finished"

End

'==============================================================================

BitSet10ms:

   'GLOBAL: SysFlags

   'Set SFTick10ms flag in SysFlags
   SysFlags = BitSet( SysFlags, 0 )

IReturn

'==============================================================================

BitSet1s:

   'GLOBAL: SysFlags

   'Set SFTick1s flag in SysFlags
   SysFlags = BitSet( SysFlags, 1 )

IReturn

'==============================================================================

Sub DecTimeout1( TimeoutCount, TimeoutEvent)

   'Has Timeout already expired ?
   If TimeoutCount > 0  Then

      ' No, decrement Timeout count
      TimeoutCount = ( TimeoutCount - 1 )

      'Has Timeout delay expired ?
      If TimeoutCount = 0  Then

         ' Yes, add Timeout event to queue 1.
         EQ1WriteSuccess( TimeoutEvent )

      ENDIF

   ENDIF

End Sub   'DecTimeout1

;==============================================================================

Sub DecTimeout2( TimeoutCount, TimeoutEvent)

   'Has Timeout already expired ?
   If TimeoutCount > 0  Then

      ' No, decrement Timeout count
      TimeoutCount = ( TimeoutCount - 1 )

      'Has Timeout delay expired ?
      If TimeoutCount = 0  Then

         ' Yes, add Timeout event to queue 2.
         EQ2WriteSuccess( TimeoutEvent )

      ENDIF

   ENDIF

End Sub   'DecTimeout2

;==============================================================================

Sub DebounceSwitch1

   'GLOBAL: Switch1DebounceCount, SysFlags

   ' Is a switch state being debounced ?
   If Switch1DebounceCount = 0  Then

      'No, Check for change of switch state.
      Local SwitchPin, SwitchBit

      SwitchBit = BitMask( SysFlags, 4 ) ' Mask SFSwitch flag, i.e. 2^2.

      SwitchPin = Pin(12)
      SwitchPin = BitToggle( SwitchPin, 0 )     ' Inputs are active low
      SwitchPin = BitShiftLeft( SwitchPin )     ' Match PIN bit position with position of SFSwitch bit in SysFags
      SwitchPin = BitShiftLeft( SwitchPin )

      ' Has switch changed state ?
      If Not( SwitchPin = SwitchBit )  Then

         'Yes, debounce switch1.
         SwitchDebounced( Switch1DebounceCount, 2, 2, 1 )

      ENDIF

   Else       'Switch1DebounceCount <> 0
      SwitchDebounced( Switch1DebounceCount, 2, 2, 1 )

   ENDIF

End Sub   'DebounceSwitch1

;==============================================================================

Sub SwitchDebounced( SwitchDebounceCount, SwitchBit, Switch1Event, Switch0Event )

   'GLOBAL: SysFlags

   SwitchDebounceCount = SwitchDebounceCount + 1

   'Has 50ms (5 x 10ms) of debounce time elapsed ?
   If ( SwitchDebounceCount = 5 )   Then

      SysFlags = BitToggle( SysFlags, SwitchBit)   ' Yes, toggle SFSwitch bit in SysFlags

      If BitTstSet( SysFlags, SwitchBit )  Then
         EQ1WriteSuccess( Switch1event )            ' Place Switch1 event in queue.
      Else
         EQ1WriteSuccess( Switch0Event )            ' Place Switch0 event in queue.
      ENDIF

      SwitchDebounceCount = 0

   ENDIF

End Sub   'SwitchDebounced

;==============================================================================

A0:   'Q1, 1, State0 - Undefined

Return

;==============================================================================

A1:   'Q1, 2, State0 - Switch0

Return

;==============================================================================

A2:   'Q1, 3, State0 - Switch1

   PrvState1 = CurState1
   CurState1 = 1

   SecTimeout1  = 2
   Switch1Count = 1

Return

;==============================================================================

A3:   'Q1, 4, State0 - TickTimeout

Return

;==============================================================================

A4:   'Q1, 5, State0 - SecondTimeout

Return

;==============================================================================

B0:   'Q1, 6, State1 - Undefined

Return

;==============================================================================

B1:   'Q1, 7, State1 - Switch0

Return

;==============================================================================

B2:   'Q1, 8, State1 - Switch1

   Switch1Count = (Switch1Count + 1 )

Return

;==============================================================================

B3:   'Q1, 9, State1 - TickTimeout

Return

;==============================================================================

B4:   'Q1, 10, State1 - SecondTimeout

   'Has Switch1 been pressed twice and still pressed after 2 seconds
   If (Switch1Count = 3)  And  BitTstSet( SysFlags, 2 )  Then

      'Toggle LED state
      SysFlags = BitToggle( SysFlags, 3)   ' Toggle SFLed flag

      If BitTstSet( SysFlags, 3 )  THEN
      '  Pin(11) = 0           'Turn LED on
         EQ2WriteSuccess( 2 )  'Send Enable2 event to event queue 2
      Else
      '  Pin(11) = 1           'Turn LED off
         EQ2WriteSuccess( 3 )  'Send Disable2 event to event queue 2
      EndIf

   ENDIF


   Switch1Count = 0

   PrvState1    = CurState1
   CurState1    = 0

Return

;==============================================================================

E0:   'Q2, 1, State0 - Undefined2

Return

;==============================================================================

E1:   'Q2, 2, State0 - TickTimeout2

Return

;==============================================================================

E2:   'Q2, 3, State0 - Enable2

   PrvState2 = CurState2
   CurState2 = 1

   TickTimeout2 = 20

   Pin(11) = 0    'Turn LED on

Return

;==============================================================================

E3:   'Q2, 4, State0 - Disable2

Return

;==============================================================================

F0:   'Q2, 5, State1 - Undefined2

Return

;==============================================================================

F1:   'Q2, 6, State1 - TickTimeout2

   PrvState2 = CurState2
   CurState2 = 2

   TickTimeout2 = 20

   Pin(11) = 1    'Turn LED off

Return

;==============================================================================

F2:   'Q2, 7, State1 - Enable2

Return

;==============================================================================

F3:   'Q2, 8, State1 - Disable2

   PrvState2 = CurState2
   CurState2 = 0

   Pin(11) = 1    'Turn LED off

Return

;==============================================================================

G0:   'Q2, 9, State2 - Undefined2

Return

;==============================================================================

G1:   'Q2,10, State2 - TickTimeout2

   PrvState2 = CurState2
   CurState2 = 1

   TickTimeout2 = 20

   Pin(11) = 0    'Turn LED on

Return

;==============================================================================

G2:   'Q2, 11, State2 - Enable2

Return

;==============================================================================

G3:   'Q2, 12, State2 - Disable2

   PrvState2 = CurState2
   CurState2 = 0

   Pin(11) = 1    'Turn LED off

Return

;==============================================================================
